import { CONSTANTS } from '../../constants/constant';

//  视图操作类
class View {
    //  构造函数
    constructor(opt = {}) {
        this.opt = opt;
        this.mindMap = this.opt.mindMap;
        this.scale = 1;
        this.sx = 0;
        this.sy = 0;
        this.x = 0;
        this.y = 0;
        this.firstDrag = true;
        this.setTransformData(this.mindMap.opt.viewData);
        this.bind();
    }

    //  绑定
    bind() {
        // 快捷键
        this.mindMap.keyCommand.addShortcut('Control+=', () => {
            this.enlarge();
        });
        this.mindMap.keyCommand.addShortcut('Control+-', () => {
            this.narrow();
        });
        this.mindMap.keyCommand.addShortcut('Control+i', () => {
            this.fit();
        });
        // 拖动视图
        this.mindMap.event.on('mousedown', (e) => {
            const { isDisableDrag, mousedownEventPreventDefault } =
                this.mindMap.opt;
            if (isDisableDrag) return;
            if (mousedownEventPreventDefault) {
                e.preventDefault();
            }
            this.sx = this.x;
            this.sy = this.y;
        });
        this.mindMap.event.on('drag', (e, event) => {
            // 按住ctrl键拖动为多选
            // 禁用拖拽
            if (e.ctrlKey || e.metaKey || this.mindMap.opt.isDisableDrag) {
                return;
            }
            if (this.firstDrag) {
                this.firstDrag = false;
                // 清除激活节点
                if (this.mindMap.renderer.activeNodeList.length > 0) {
                    this.mindMap.execCommand('CLEAR_ACTIVE_NODE');
                }
            }
            this.x = this.sx + event.mousemoveOffset.x;
            this.y = this.sy + event.mousemoveOffset.y;
            this.transform();
        });
        this.mindMap.event.on('mouseup', () => {
            this.firstDrag = true;
        });
        // 放大缩小视图
        this.mindMap.event.on('mousewheel', (e, dirs, event, isTouchPad) => {
            const {
                customHandleMousewheel,
                mousewheelAction,
                mouseScaleCenterUseMousePosition,
                mousewheelMoveStep,
                mousewheelZoomActionReverse,
                disableMouseWheelZoom,
                translateRatio,
            } = this.mindMap.opt;
            // 是否自定义鼠标滚轮事件
            if (
                customHandleMousewheel &&
                typeof customHandleMousewheel === 'function'
            ) {
                return customHandleMousewheel(e);
            }
            // 1.鼠标滚轮事件控制缩放
            if (
                mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM ||
                e.ctrlKey ||
                e.metaKey
            ) {
                if (disableMouseWheelZoom) return;
                const { x: clientX, y: clientY } = this.mindMap.toPos(
                    e.clientX,
                    e.clientY,
                );
                const cx = mouseScaleCenterUseMousePosition
                    ? clientX
                    : undefined;
                const cy = mouseScaleCenterUseMousePosition
                    ? clientY
                    : undefined;
                // 如果来自触控板，那么过滤掉左右的移动
                if (
                    isTouchPad &&
                    (dirs.includes(CONSTANTS.DIR.LEFT) ||
                        dirs.includes(CONSTANTS.DIR.RIGHT))
                ) {
                    dirs = dirs.filter((dir) => {
                        return ![
                            CONSTANTS.DIR.LEFT,
                            CONSTANTS.DIR.RIGHT,
                        ].includes(dir);
                    });
                }
                switch (true) {
                    // 鼠标滚轮，向上和向左，都是缩小
                    case dirs.includes(CONSTANTS.DIR.UP || CONSTANTS.DIR.LEFT):
                        mousewheelZoomActionReverse
                            ? this.enlarge(cx, cy, isTouchPad)
                            : this.narrow(cx, cy, isTouchPad);
                        break;
                    // 鼠标滚轮，向下和向右，都是放大
                    case dirs.includes(
                        CONSTANTS.DIR.DOWN || CONSTANTS.DIR.RIGHT,
                    ):
                        mousewheelZoomActionReverse
                            ? this.narrow(cx, cy, isTouchPad)
                            : this.enlarge(cx, cy, isTouchPad);
                        break;
                }
            } else {
                // 2.鼠标滚轮事件控制画布移动
                let stepX = 0;
                let stepY = 0;
                if (isTouchPad) {
                    // 如果是触控板，那么直接使用触控板滑动距离
                    stepX = Math.abs(e.wheelDeltaX);
                    stepY = Math.abs(e.wheelDeltaY);
                } else {
                    stepX = stepY = mousewheelMoveStep;
                }
                let mx = 0;
                let my = 0;
                // 上移
                if (dirs.includes(CONSTANTS.DIR.DOWN)) {
                    my = -stepY;
                }
                // 下移
                if (dirs.includes(CONSTANTS.DIR.UP)) {
                    my = stepY;
                }
                // 右移
                if (dirs.includes(CONSTANTS.DIR.LEFT)) {
                    mx = stepX;
                }
                // 左移
                if (dirs.includes(CONSTANTS.DIR.RIGHT)) {
                    mx = -stepX;
                }
                this.translateXY(mx * translateRatio, my * translateRatio);
            }
        });
        this.mindMap.on('resize', () => {
            if (!this.checkNeedMindMapInCanvas()) return;
            this.transform();
        });
    }

    //  获取当前变换状态数据
    getTransformData() {
        return {
            transform: this.mindMap.draw.transform(),
            state: {
                scale: this.scale,
                x: this.x,
                y: this.y,
                sx: this.sx,
                sy: this.sy,
            },
        };
    }

    //  动态设置变换状态数据
    setTransformData(viewData) {
        if (viewData) {
            Object.keys(viewData.state).forEach((prop) => {
                this[prop] = viewData.state[prop];
            });
            this.mindMap.draw.transform({
                ...viewData.transform,
            });
            this.mindMap.emit('view_data_change', this.getTransformData());
            this.emitEvent('scale');
            this.emitEvent('translate');
        }
    }

    //  平移x,y方向
    translateXY(x, y) {
        if (x === 0 && y === 0) return;
        this.x += x;
        this.y += y;
        this.transform();
        this.emitEvent('translate');
    }

    //  平移x方向
    translateX(step) {
        if (step === 0) return;
        this.x += step;
        this.transform();
        this.emitEvent('translate');
    }

    //  平移x方式到
    translateXTo(x) {
        this.x = x;
        this.transform();
        this.emitEvent('translate');
    }

    //  平移y方向
    translateY(step) {
        if (step === 0) return;
        this.y += step;
        this.transform();
        this.emitEvent('translate');
    }

    //  平移y方向到
    translateYTo(y) {
        this.y = y;
        this.transform();
        this.emitEvent('translate');
    }

    //   应用变换
    transform() {
        try {
            this.limitMindMapInCanvas();
        } catch (error) {}
        this.mindMap.draw.transform({
            origin: [0, 0],
            scale: this.scale,
            translate: [this.x, this.y],
        });
        this.mindMap.emit('view_data_change', this.getTransformData());
    }

    //  恢复
    reset() {
        const scaleChange = this.scale !== 1;
        const translateChange = this.x !== 0 || this.y !== 0;
        this.scale = 1;
        this.x = 0;
        this.y = 0;
        this.transform();
        if (scaleChange) {
            this.emitEvent('scale');
        }
        if (translateChange) {
            this.emitEvent('translate');
        }
    }

    //  缩小
    narrow(cx, cy, isTouchPad) {
        let { scaleRatio, minZoomRatio } = this.mindMap.opt;
        scaleRatio = scaleRatio / (isTouchPad ? 5 : 1);
        const scale = Math.max(this.scale - scaleRatio, minZoomRatio / 100);
        this.scaleInCenter(scale, cx, cy);
        this.transform();
        this.emitEvent('scale');
    }

    //  放大
    enlarge(cx, cy, isTouchPad) {
        let { scaleRatio, maxZoomRatio } = this.mindMap.opt;
        scaleRatio = scaleRatio / (isTouchPad ? 5 : 1);
        let scale = 0;
        if (maxZoomRatio === -1) {
            scale = this.scale + scaleRatio;
        } else {
            scale = Math.min(this.scale + scaleRatio, maxZoomRatio / 100);
        }
        this.scaleInCenter(scale, cx, cy);
        this.transform();
        this.emitEvent('scale');
    }

    // 基于指定中心进行缩放，cx，cy 可不指定，此时会使用画布中心点
    scaleInCenter(scale, cx, cy) {
        if (cx === undefined || cy === undefined) {
            cx = this.mindMap.width / 2;
            cy = this.mindMap.height / 2;
        }
        const prevScale = this.scale;
        const ratio = 1 - scale / prevScale;
        const dx = (cx - this.x) * ratio;
        const dy = (cy - this.y) * ratio;
        this.x += dx;
        this.y += dy;
        this.scale = scale;
    }

    //  设置缩放
    setScale(scale, cx, cy) {
        if (cx !== undefined && cy !== undefined) {
            this.scaleInCenter(scale, cx, cy);
        } else {
            this.scale = scale;
        }
        this.transform();
        this.emitEvent('scale');
    }

    // 适应画布大小
    fit(getRbox = () => {}, enlarge = false, fitPadding) {
        fitPadding =
            fitPadding === undefined ? this.mindMap.opt.fitPadding : fitPadding;
        const draw = this.mindMap.draw;
        const origTransform = draw.transform();
        const rect = getRbox() || draw.rbox();
        const drawWidth = rect.width / origTransform.scaleX;
        const drawHeight = rect.height / origTransform.scaleY;
        const drawRatio = drawWidth / drawHeight;
        let { width: elWidth, height: elHeight } = this.mindMap.elRect;
        elWidth = elWidth - fitPadding * 2;
        elHeight = elHeight - fitPadding * 2;
        const elRatio = elWidth / elHeight;
        let newScale = 0;
        let flag = '';
        if (drawWidth <= elWidth && drawHeight <= elHeight && !enlarge) {
            newScale = 1;
            flag = 1;
        } else {
            let newWidth = 0;
            let newHeight = 0;
            if (drawRatio > elRatio) {
                newWidth = elWidth;
                newHeight = elWidth / drawRatio;
                flag = 2;
            } else {
                newHeight = elHeight;
                newWidth = elHeight * drawRatio;
                flag = 3;
            }
            newScale = newWidth / drawWidth;
        }
        this.setScale(newScale);
        const newRect = getRbox() || draw.rbox();
        // 需要考虑画布容器距浏览器窗口左上角的距离
        newRect.x -= this.mindMap.elRect.left;
        newRect.y -= this.mindMap.elRect.top;
        let newX = 0;
        let newY = 0;
        if (flag === 1) {
            newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2;
            newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2;
        } else if (flag === 2) {
            newX = -newRect.x + fitPadding;
            newY = -newRect.y + fitPadding + (elHeight - newRect.height) / 2;
        } else if (flag === 3) {
            newX = -newRect.x + fitPadding + (elWidth - newRect.width) / 2;
            newY = -newRect.y + fitPadding;
        }
        this.translateXY(newX, newY);
    }

    // 判断是否需要将思维导图限制在画布内
    checkNeedMindMapInCanvas() {
        // 如果当前在演示模式，那么不需要限制
        if (
            this.mindMap.demonstrate &&
            this.mindMap.demonstrate.isInDemonstrate
        ) {
            return false;
        }
        const {
            isLimitMindMapInCanvasWhenHasScrollbar,
            isLimitMindMapInCanvas,
        } = this.mindMap.opt;
        // 如果注册了滚动条插件，那么使用isLimitMindMapInCanvasWhenHasScrollbar配置
        if (this.mindMap.scrollbar) {
            return isLimitMindMapInCanvasWhenHasScrollbar;
        } else {
            // 否则使用isLimitMindMapInCanvas配置
            return isLimitMindMapInCanvas;
        }
    }

    // 将思维导图限制在画布内
    limitMindMapInCanvas() {
        if (!this.checkNeedMindMapInCanvas()) return;

        let { scale, left, top, right, bottom } = this.getPositionLimit();

        // 画布宽高改变了，但是思维导图元素变换的中心点依旧是原有位置，所以需要加上中心点变化量
        const centerXChange =
            ((this.mindMap.width - this.mindMap.initWidth) / 2) * scale;
        const centerYChange =
            ((this.mindMap.height - this.mindMap.initHeight) / 2) * scale;

        // 如果缩放值改变了
        const scaleRatio = this.scale / scale;
        left *= scaleRatio;
        right *= scaleRatio;
        top *= scaleRatio;
        bottom *= scaleRatio;

        // 加上画布中心点距离
        const centerX = this.mindMap.width / 2;
        const centerY = this.mindMap.height / 2;
        const scaleOffset = this.scale - 1;
        left -= scaleOffset * centerX - centerXChange;
        right -= scaleOffset * centerX - centerXChange;
        top -= scaleOffset * centerY - centerYChange;
        bottom -= scaleOffset * centerY - centerYChange;

        // 判断是否超出边界
        if (this.x > left) {
            this.x = left;
        }
        if (this.x < right) {
            this.x = right;
        }
        if (this.y > top) {
            this.y = top;
        }
        if (this.y < bottom) {
            this.y = bottom;
        }
    }

    // 计算图形四个方向的位置边界值
    getPositionLimit() {
        const { scaleX, scaleY } = this.mindMap.draw.transform();
        const drawRect = this.mindMap.draw.rbox();
        const rootRect = this.mindMap.renderer.root.group.rbox();
        const rootCenterOffset =
            this.mindMap.renderer.layout.getRootCenterOffset(
                rootRect.width,
                rootRect.height,
            );
        const left = rootRect.x - drawRect.x - rootCenterOffset.x * scaleX;
        const right = rootRect.x - drawRect.x2 - rootCenterOffset.x * scaleX;
        const top = rootRect.y - drawRect.y - rootCenterOffset.y * scaleY;
        const bottom = rootRect.y - drawRect.y2 - rootCenterOffset.y * scaleY;
        return {
            scale: scaleX,
            left,
            right,
            top,
            bottom,
        };
    }

    // 派发事件
    emitEvent(type) {
        switch (type) {
            case 'scale':
                this.mindMap.emit('scale', this.scale);
            case 'translate':
                this.mindMap.emit('translate', this.x, this.y);
        }
    }
}

export default View;
